/**
 * Copyright (C) 2009 IcyRiver / Wesley He
 *
 * IcyRiver Lib  may be copied only under the terms of the GNU General
 * Public License V1, which may be found in the IcyRiver.net source kit.
 * Please visit the IcyRiver Home Page http://icyriver.net/ for more detail.
 **/

//ngx_http_icyriver_module.c

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

typedef struct {
    u_int   max_radius;
    u_int   min_radius;
    u_int   step_radius;
    unsigned char** icyriver_templates;
    size_t* icyriver_sizes;
    ngx_flag_t           enable;
} ngx_http_icyriver_loc_conf_t;


static ngx_command_t  ngx_http_icyriver_commands[] = {
    { ngx_string("icyriver"),
      NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
      ngx_http_icyriver,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },

    { ngx_string("icyriver_min_radius"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_num_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_icyriver_loc_conf_t, min_radius),
      NULL },

    { ngx_string("icyriver_max_radius"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_num_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_icyriver_loc_conf_t, max_radius),
      NULL },

    { ngx_string("icyriver_step_radius"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_num_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_icyriver_loc_conf_t, step_radius),
      NULL },

      ngx_null_command
};


static ngx_http_module_t  ngx_http_icyriver_module_ctx = {
    NULL,                          /* preconfiguration */
    NULL,           /* postconfiguration */

    NULL,                          /* create main configuration */
    NULL,                          /* init main configuration */

    NULL,                          /* create server configuration */
    NULL,                          /* merge server configuration */

    ngx_http_icyriver_create_loc_conf,  /* create location configuration */
    ngx_http_icyriver_merge_loc_conf /* merge location configuration */
};


static void *
ngx_http_icyriver_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_icyriver_loc_conf_t  *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_icyriver_loc_conf_t));
    if (conf == NULL) {
        return NGX_CONF_ERROR;
    }
    conf->min_radius = NGX_CONF_UNSET_UINT;
    conf->max_radius = NGX_CONF_UNSET_UINT;
    conf->step_radius = NGX_CONF_UNSET_UINT;
    conf->enable = NGX_CONF_UNSET;
    return conf;
}

static char *
ngx_http_icyriver_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_icyriver_loc_conf_t *prev = parent;
    ngx_http_icyriver_loc_conf_t *conf = child;

    ngx_conf_merge_uint_value(conf->min_radius, prev->min_radius, 10);
    ngx_conf_merge_uint_value(conf->max_radius, prev->max_radius, 20);
    ngx_conf_merge_uint_value(conf->step_radius, prev->step_radius, 2);
    ngx_conf_merge_value(conf->enable, prev->enable, 0);

    if (conf->min_radius < 1) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "min_radius must be equal or more than 1"); 
        return NGX_CONF_ERROR;
    }
    if (conf->max_radius < conf->min_radius) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "max_radius must be equal or more than min_radius"); 
        return NGX_CONF_ERROR;
    }

    if(conf->enable)
        ngx_http_icyriver_init(conf);

    return NGX_CONF_OK;
}



ngx_module_t  ngx_http_icyriver_module = {
    NGX_MODULE_V1,
    &ngx_http_icyriver_module_ctx, /* module context */
    ngx_http_icyriver_commands,   /* module directives */
    NGX_HTTP_MODULE,               /* module type */
    NULL,                          /* init master */
    NULL,                          /* init module */
    NULL,                          /* init process */
    NULL,                          /* init thread */
    NULL,                          /* exit thread */
    NULL,                          /* exit process */
    NULL,                          /* exit master */
    NGX_MODULE_V1_PADDING
};


static char *
ngx_http_icyriver(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t  *clcf;
    ngx_http_icyriver_loc_conf_t *cglcf = conf;

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_icyriver_handler;

    cglcf->enable = 1;

    return NGX_CONF_OK;
}


static ngx_int_t
ngx_http_icyriver_handler(ngx_http_request_t *r)
{
    ngx_int_t     rc;
    ngx_buf_t    *b;
    ngx_chain_t   out;
    unsigned char *image;
    // all right, let's figure out what they're asking for
    u_int bg_color, fg_color, req_radius = 0;
    size_t i;
    char *digit;
    unsigned char bg[3]; 
    unsigned char fg[3];

    ngx_http_icyriver_loc_conf_t  *cglcf;
    cglcf = ngx_http_get_module_loc_conf(r, ngx_http_icyriver_module);

    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
        return NGX_HTTP_NOT_ALLOWED;
    }

    rc = ngx_http_discard_body(r);

    if (rc != NGX_OK && rc != NGX_AGAIN) {
        return rc;
    }

    if (r->headers_in.if_modified_since) {
        return NGX_HTTP_NOT_MODIFIED;
    }

    digit = (char *)r->uri.data + r->uri.len - 1;

    if (!(*digit-- == 'f' && *digit-- == 'i' && *digit-- == 'g' && *digit-- == '.')) {
	ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Invalid extension with %s", digit);
        return NGX_HTTP_NOT_FOUND;
    }
    i = 0;
    while(*digit >= '0' && *digit <= '9' && i < (r->uri.len - 7 - 7 - 4)) {
        req_radius += (*digit-- - '0')*pow(10, i++);
    }
    fg_color   = strtol(digit -= 6, NULL, 16); // "XXXXXX"
    bg_color   = strtol(digit -= 7, NULL, 16); // "XXXXXX/"

    bg[RED] = bg_color >> 16;
    bg[GREEN] = bg_color >> 8;
    bg[BLUE] = bg_color;

    fg[RED] = fg_color >> 16;
    fg[GREEN] = fg_color >> 8;
    fg[BLUE] = fg_color;

    r->headers_out.content_type.len = sizeof("image/gif") - 1;
    r->headers_out.content_type.data = (u_char *) "image/gif";

    if (req_radius < cglcf->min_radius || req_radius > cglcf->max_radius) {
	ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Invalid radius %ui", req_radius);
        return NGX_HTTP_NOT_FOUND;
    }

    int radius_index = radius2index(req_radius, cglcf);

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = cglcf->circle_sizes[radius_index];

    if (r->method == NGX_HTTP_HEAD) {
        rc = ngx_http_send_header(r);

        if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
            return rc;
        }
    }

    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    if (b == NULL) {
	ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate response buffer.");
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    out.buf = b;
    out.next = NULL;

    image = ngx_palloc(r->pool, cglcf->circle_sizes[radius_index]);
    if (image == NULL) {
      ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate memory for circle image.");
      return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    ngx_memcpy(image, cglcf->circle_templates[radius_index], cglcf->circle_sizes[radius_index]);
    ngx_http_icyriver_colorize(image, bg, fg);

    b->pos = image;
    b->last = image + cglcf->circle_sizes[radius_index];

    b->memory = 1;
    b->last_buf = 1;

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }

    return ngx_http_output_filter(r, &out);
}
